﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PasswordGeneratorViewModel.cs" company="Marcin Jastrzebski">
//   Copyright (c) Marcin Jastrzebski (marcin@marcinj.net)
// </copyright>
// <summary>
//   The password generator view model.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace mPassRT.ViewModels.PasswordGenerator
{
    using System;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Windows.Input;

    using KeePassLib.Cryptography.PasswordGenerator;
    using KeePassLib.Security;

    using mPassRT.Common;

    using PopupLibrary;

    using Windows.UI.Popups;

    /// <summary>The password generator view model.</summary>
    public class PasswordGeneratorViewModel : BindableBase
    {
        #region Fields

        /// <summary>The generate password command.</summary>
        private readonly Command generatePasswordCommand;

        /// <summary>The set password command.</summary>
        private readonly Command setPasswordCommand;

        /// <summary>The additional characters.</summary>
        private string additionalCharacters;

        /// <summary>The generated password.</summary>
        private string generatedPassword;

        /// <summary>The included characters.</summary>
        private CharSet includedCharacters;

        /// <summary>The is char set generation enabled.</summary>
        private bool isCharSetGenerationEnabled;

        /// <summary>The is pattern generation enabled.</summary>
        private bool isPatternGenerationEnabled;

        /// <summary>The is random character reordering enabled.</summary>
        private bool isRandomCharacterReorderingEnabled;

        /// <summary>The password length.</summary>
        private int passwordLength;

        /// <summary>The password pattern.</summary>
        private string passwordPattern;

        #endregion

        #region Constructors and Destructors

        /// <summary>Initializes a new instance of the <see cref="PasswordGeneratorViewModel"/> class.</summary>
        public PasswordGeneratorViewModel()
        {
            this.generatePasswordCommand = new Command
                                               {
                                                   CanExecuteHandler = this.CanGeneratePassword, 
                                                   ExecuteHandler = this.GeneratePassword
                                               };

            this.setPasswordCommand = new Command
                                          {
                                              CanExecuteHandler = this.CanSetPassword, 
                                              ExecuteHandler = this.SetPassword
                                          };

            this.PasswordLength = 16;
            this.IncludeUppercaseLetters = true;
            this.IncludeLowercaseLetters = true;
            this.IncludeDigits = true;
            this.AdditionalCharacters = string.Empty;

            this.PasswordPattern = string.Empty;
            this.IsRandomCharacterReorderingEnabled = false;

            this.SetGeneratorType(PasswordGeneratorType.CharSet);
        }

        #endregion

        #region Public Properties

        /// <summary>Gets or sets the additional characters.</summary>
        public string AdditionalCharacters
        {
            get
            {
                return this.additionalCharacters;
            }

            set
            {
                this.SetProperty(ref this.additionalCharacters, value);
                this.generatePasswordCommand.OnCanExecuteChanged();
            }
        }

        /// <summary>Gets the cancel command.</summary>
        public ICommand CancelCommand
        {
            get
            {
                return new Command { ExecuteHandler = this.Cancel };
            }
        }

        /// <summary>Gets the generate password command.</summary>
        public ICommand GeneratePasswordCommand
        {
            get
            {
                return this.generatePasswordCommand;
            }
        }

        /// <summary>Gets or sets the generated password.</summary>
        public string GeneratedPassword
        {
            get
            {
                return this.generatedPassword;
            }

            set
            {
                this.SetProperty(ref this.generatedPassword, value);
                this.setPasswordCommand.OnCanExecuteChanged();
            }
        }

        /// <summary>Gets or sets a value indicating whether brackets are included.</summary>
        public bool IncludeBrackets
        {
            get
            {
                return this.GetIncludedCharState(CharSet.Brackets);
            }

            set
            {
                this.SetIncludedCharState(CharSet.Brackets, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether digits are included.</summary>
        public bool IncludeDigits
        {
            get
            {
                return this.GetIncludedCharState(CharSet.Digits);
            }

            set
            {
                this.SetIncludedCharState(CharSet.Digits, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether high ANSI characters are included.</summary>
        public bool IncludeHighAnsiCharacters
        {
            get
            {
                return this.GetIncludedCharState(CharSet.HighAnsi);
            }

            set
            {
                this.SetIncludedCharState(CharSet.HighAnsi, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether lowercase letters are included.</summary>
        public bool IncludeLowercaseLetters
        {
            get
            {
                return this.GetIncludedCharState(CharSet.LowercaseLetters);
            }

            set
            {
                this.SetIncludedCharState(CharSet.LowercaseLetters, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether is included.</summary>
        public bool IncludeMinus
        {
            get
            {
                return this.GetIncludedCharState(CharSet.Minus);
            }

            set
            {
                this.SetIncludedCharState(CharSet.Minus, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether space is included.</summary>
        public bool IncludeSpace
        {
            get
            {
                return this.GetIncludedCharState(CharSet.Space);
            }

            set
            {
                this.SetIncludedCharState(CharSet.Space, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether special characters are included.</summary>
        public bool IncludeSpecialCharacters
        {
            get
            {
                return this.GetIncludedCharState(CharSet.SpecialCharacters);
            }

            set
            {
                this.SetIncludedCharState(CharSet.SpecialCharacters, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether underscore is included.</summary>
        public bool IncludeUnderscore
        {
            get
            {
                return this.GetIncludedCharState(CharSet.Underscore);
            }

            set
            {
                this.SetIncludedCharState(CharSet.Underscore, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether uppercase letters are included.</summary>
        public bool IncludeUppercaseLetters
        {
            get
            {
                return this.GetIncludedCharState(CharSet.UppercaseLetters);
            }

            set
            {
                this.SetIncludedCharState(CharSet.UppercaseLetters, value);
            }
        }

        /// <summary>Gets a value indicating whether char set generation is enabled.</summary>
        public bool IsCharSetGenerationEnabled
        {
            get
            {
                return this.isCharSetGenerationEnabled;
            }

            private set
            {
                this.SetProperty(ref this.isCharSetGenerationEnabled, value);
            }
        }

        /// <summary>Gets a value indicating whether pattern generation is enabled.</summary>
        public bool IsPatternGenerationEnabled
        {
            get
            {
                return this.isPatternGenerationEnabled;
            }

            private set
            {
                this.SetProperty(ref this.isPatternGenerationEnabled, value);
            }
        }

        /// <summary>Gets or sets a value indicating whether if random character reordering is enabled.</summary>
        public bool IsRandomCharacterReorderingEnabled
        {
            get
            {
                return this.isRandomCharacterReorderingEnabled;
            }

            set
            {
                this.SetProperty(ref this.isRandomCharacterReorderingEnabled, value);
            }
        }

        /// <summary>Gets or sets the password length.</summary>
        public int PasswordLength
        {
            get
            {
                return this.passwordLength;
            }

            set
            {
                this.SetProperty(ref this.passwordLength, value);
                this.generatePasswordCommand.OnCanExecuteChanged();
            }
        }

        /// <summary>Gets or sets the password pattern.</summary>
        public string PasswordPattern
        {
            get
            {
                return this.passwordPattern;
            }

            set
            {
                this.SetProperty(ref this.passwordPattern, value);
                this.generatePasswordCommand.OnCanExecuteChanged();
            }
        }

        /// <summary>Gets the set generator type command.</summary>
        public ICommand SetGeneratorTypeCommand
        {
            get
            {
                return new Command { ExecuteHandler = this.SetGeneratorType };
            }
        }

        /// <summary>Gets the close command.</summary>
        public ICommand SetPasswordCommand
        {
            get
            {
                return this.setPasswordCommand;
            }
        }

        #endregion

        #region Methods

        /// <summary>Determines if the password can be generated.</summary>
        /// <param name="arg">The parameter is not used.</param>
        /// <returns>The <see cref="bool"/>.</returns>
        private bool CanGeneratePassword(object arg)
        {
            if (this.IsCharSetGenerationEnabled)
            {
                return this.PasswordLength > 0
                       && (this.includedCharacters != CharSet.None
                           || string.IsNullOrEmpty(this.AdditionalCharacters) == false);
            }

            if (this.IsPatternGenerationEnabled)
            {
                return string.IsNullOrEmpty(this.PasswordPattern) == false;
            }

            return false;
        }

        /// <summary>Determines if the password can be returned back.</summary>
        /// <param name="arg">The parameter is not used.</param>
        /// <returns>The <see cref="bool"/>.</returns>
        private bool CanSetPassword(object arg)
        {
            return string.IsNullOrEmpty(this.GeneratedPassword) == false;
        }

        /// <summary>Closes the dialog without returning the password.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private async void Cancel(object obj)
        {
            await PopupManager.Current.CloseAsync(null);
        }

        /// <summary>Creates a character set from the settings.</summary>
        /// <returns>The <see cref="PwCharSet"/>.</returns>
        private PwCharSet CreateCharSet()
        {
            var charset = new PwCharSet();

            // add character sets as configured
            var supportedCharSets = new[]
                                        {
                                            CharSet.UppercaseLetters, CharSet.LowercaseLetters, CharSet.Digits, 
                                            CharSet.Space, CharSet.Minus, CharSet.Underscore, CharSet.Brackets, 
                                            CharSet.SpecialCharacters, CharSet.HighAnsi
                                        };
            foreach (string charSetString in
                supportedCharSets.Where(this.GetIncludedCharState).Select(cs => cs.ToCharSetString()))
            {
                charset.Add(charSetString);
            }

            // add additional characters
            charset.Add(this.AdditionalCharacters);

            return charset;
        }

        /// <summary>Create a password profile from the settings.</summary>
        /// <returns>The <see cref="PwProfile"/>.</returns>
        private PwProfile CreateProfile()
        {
            var profile = new PwProfile();

            profile.GeneratorType = this.IsCharSetGenerationEnabled
                                        ? PasswordGeneratorType.CharSet
                                        : PasswordGeneratorType.Pattern;

            profile.Length = (uint)this.PasswordLength;
            profile.CharSet = this.CreateCharSet();

            profile.Pattern = this.PasswordPattern;
            profile.PatternPermutePassword = this.isRandomCharacterReorderingEnabled;

            return profile;
        }

        /// <summary>Generates the password using current settings.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private async void GeneratePassword(object obj)
        {
            ProtectedString password;
            PwProfile profile = this.CreateProfile();
            PwgError status = PwGenerator.Generate(out password, profile, null, new CustomPwGeneratorPool());

            string message;
            switch (status)
            {
                case PwgError.Success:
                    this.GeneratedPassword = password.ReadString();
                    return;

                case PwgError.TooFewCharacters:
                    message = "Not enough characters to generate a password.";
                    break;

                case PwgError.UnknownAlgorithm:
                    message = "Unknown password generation algorithm.";
                    break;

                default:
                    message = "Unable to generate a new password.";
                    break;
            }

            var dialog = new MessageDialog(message);
            await dialog.ShowAsync();
        }

        /// <summary>The get included char state.</summary>
        /// <param name="charset">The charset.</param>
        /// <returns>The <see cref="bool"/>.</returns>
        private bool GetIncludedCharState(CharSet charset)
        {
            return this.includedCharacters.HasFlag(charset);
        }

        /// <summary>Sets the generator type.</summary>
        /// <param name="obj">The generator type.</param>
        private void SetGeneratorType(object obj)
        {
            var generatorType = (PasswordGeneratorType)obj;

            bool charset = generatorType == PasswordGeneratorType.CharSet;

            this.IsCharSetGenerationEnabled = charset;
            this.IsPatternGenerationEnabled = charset == false;

            this.generatePasswordCommand.OnCanExecuteChanged();
        }

        /// <summary>The set included char state.</summary>
        /// <param name="flag">The flag.</param>
        /// <param name="value">The value.</param>
        /// <param name="property">The property.</param>
        private void SetIncludedCharState(CharSet flag, bool value, [CallerMemberName] string property = null)
        {
            this.SetProperty(ref this.includedCharacters, this.includedCharacters.Set(flag, value), property);
            this.generatePasswordCommand.OnCanExecuteChanged();
        }

        /// <summary>Returns the password to the caller.</summary>
        /// <param name="obj">The parameter is not used.</param>
        private async void SetPassword(object obj)
        {
            await PopupManager.Current.CloseAsync(this.GeneratedPassword);
        }

        #endregion
    }
}